package com.zms.maze;


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import com.zms.maze.MummyConst.Direction;

import static java.lang.Math.*;

public class Maze implements Cloneable{

	public static int StartX=70;
	public static int StartY=70;
	public static int BlockWidth=60;
	public static int BlockHeight=60;
	
	public static int WallWidtha=12;
	public static int WallHeighta=78;
	public static int WallWidthb=72;
	public static int WallHeightb=18;
	public static int ToggleWidthb=54;
	public static int WallWidthc=12;
	public static int WallHeightc=75;
	public static int KeyWidth=36;
	public static int KeyHeight=36;
	
	public int blockrows;
	public int blockcolumns;
	public int manx;
	public int many;	
	public int doorx;
	public int doory;
	public Explorer explorer;
	public List<Mummy> mummies=new ArrayList<Mummy>();
	public List<Line> lines=new ArrayList<Line>();
	public List<Scorpion> scorpions=new ArrayList<Scorpion>();
	public List<Trap> traps=new ArrayList<Trap>();
	public List<ToggleLine> toggles=new ArrayList<ToggleLine>();
	
	public Hashtable<Integer, Mummy> mummiesHash=new Hashtable<>();
	public Hashtable<Integer, Scorpion> scorpionsHash=new Hashtable<>();
	public Hashtable<Integer, ToggleLine> togglesHash=new Hashtable<>();
	
	protected Maze clone() 
	{
		try
		{
		Mummy mmTemp;
		Scorpion scoTemp;
		ToggleLine toTemp;
		Trap trapTemp;
        Maze c=new Maze();
        c.explorer=(Explorer) this.explorer.clone();
        c.lines=this.lines;
        c.blockcolumns=this.blockcolumns;
        c.blockrows=this.blockrows;
        c.doorx=this.doorx;
        c.doory=this.doory;
        c.WallHeighta=this.WallHeighta;
        c.WallHeightb=this.WallHeightb;
        c.WallHeightc=this.WallHeightc;
        c.WallWidtha=this.WallWidtha;
        c.WallWidthb=this.WallWidthb;
        c.WallWidthc=this.WallWidthc;
        for(int i=0;i<this.mummiesHash.size();i++)
        {
        	mmTemp=(Mummy) this.mummiesHash.get(i).clone();
        	c.mummiesHash.put(i,mmTemp);
        	c.mummies.add(mmTemp);
        }
        for(int i=0;i<this.scorpions.size();i++)
        {
        	scoTemp=(Scorpion) this.scorpionsHash.get(i).clone();
        	c.scorpionsHash.put(i,scoTemp);
        	c.scorpions.add(scoTemp);
        }
        for(int i=0;i<this.togglesHash.size();i++)
        {
        	toTemp=(ToggleLine) this.togglesHash.get(i).clone();
        	c.togglesHash.put(i,toTemp);
        	c.toggles.add(toTemp);
        }
        for(int i=0;i<this.traps.size();i++)
        {
        	trapTemp=(Trap) this.traps.get(i);
        	c.traps.add(trapTemp);
        }
        
        return c;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}
	public void setManPos(int x,int y)
	{
		this.manx=x;
		this.many=y;
	}
	
	public boolean equals(Object obj) {
	    if (!(obj instanceof Maze)) {
	        return false;
	    } else {
	    	Maze that = (Maze)obj;
	        return this.createSignature().equals(that.createSignature());
	    }
	}
	public int hashCode()
	{
		return 0;
	}
	
	public Explorer getExplorer()
	{
		return explorer;
	}
	
	public static int getCount() {
		File file=new File(MazeContextListener.parentPath,"./WEB-INF/data/");
		if(file.exists()) {
			return file.list().length;
		} else {
			return 0;
		}
	}
	public static Maze getMaze(int n) {
		File file=new File(MazeContextListener.parentPath,"./WEB-INF/data/"+n+".dat");
		FileInputStream input=null;
		try {
			input=new FileInputStream(file);
			ObjectInputStream in=new ObjectInputStream(input);
			return Maze.readObject(in);
		} catch (FileNotFoundException e) {
			return getFlag();
		} catch (Exception e) {
			e.printStackTrace();
			return getFlag();
		} finally {
			if(input!=null) {
				try {
					input.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	public static Maze getFlag() {
		Maze maze=new Maze();
		maze.blockcolumns=9;
		maze.blockrows=9;
		maze.manx=4;
		maze.many=4;
		maze.doorx=9;
		maze.doory=4;
		maze.mummies.add(new Mummy(4,3,MummyType.White));
		maze.mummies.add(new Mummy(5,4,MummyType.Red));
		maze.mummies.add(new Mummy(4,5,MummyType.White));
		maze.mummies.add(new Mummy(3,4,MummyType.Red));		
		maze.scorpions.add(new Scorpion(5,3));		
		maze.scorpions.add(new Scorpion(3,5));
		maze.scorpions.add(new Scorpion(3,3));
		maze.scorpions.add(new Scorpion(5,5));
		for(int i=0;i<maze.blockrows;i++) {
			maze.traps.add(new Trap(0,i));
			maze.traps.add(new Trap(maze.blockcolumns-1,i));
		}
		for(int j=1;j<maze.blockcolumns;j++) {
			maze.traps.add(new Trap(j,0));
			maze.traps.add(new Trap(j,maze.blockrows-1));
		}
		maze.lines.add(new Line(2,2,2,7));
		maze.lines.add(new Line(2,7,7,7));
		maze.lines.add(new Line(7,7,7,2));
		maze.lines.add(new Line(7,2,2,2));
		maze.validLines();		
		return maze;
	}
	
	public void validLines() {
		for(int i=0;i<lines.size();i++) {
			Line line=lines.get(i);
			if(line.x1!=line.x2 && line.y1!=line.y2) {
				lines.remove(line);
				i--;
				continue;
			}			
		}
		for(int i=0;i<lines.size();i++) {
			Line line=lines.get(i);
			if(line.isVertical()) {
				if(line.y2-line.y1>1) {
					for(int j=line.y1;j<line.y2;j++) {
						lines.add(new Line(line.x1,j,line.x2,j+1));						
					}
					lines.remove(line);
					i--;
					
				}
			} else {
				if(line.x2-line.x1>1) {
					for(int j=line.x1;j<line.x2;j++) {
						lines.add(new Line(j,line.y1,j+1,line.y2));						
					}
					lines.remove(line);
					i--;
				}
			}
		}
		for(int i=0;i<lines.size();i++) {
			Line line=lines.get(i);			
			if(line.x1==line.x2 && line.y1==line.y2) {
				lines.remove(line);
				i--;
				continue;
			}
		}
		out:for(int i=0;i<lines.size();i++) {
			for(int j=0;j<lines.size();j++) {
				if(i!=j) {
					if(lines.get(i).equals(lines.get(j))) {
						lines.remove(min(i,j));
						lines.remove(max(i,j)-1);
						i--;
						continue out;
					}
				}
			}
		}
		out:for(ToggleLine line:toggles) {
			for(int i=0;i<lines.size();i++) {
				if(line.equals(lines.get(i))) {
					lines.remove(i);
					continue out;
				}
			}
		}
		/*
		out:for(int i=0;i<lines.size();i++) {
			for(int j=0;j<lines.size();j++) {
				if(i!=j) {
					Line line1=lines.get(i);
					Line line2=lines.get(j);
					if(line1.x1==line2.x1 && line1.x1==line1.x2 && line2.x1==line2.x2) {
						if(line1.y1>=line2.y2 && line1.y2>=line2.y2) {
							continue;
						}
						if(line1.y1<=line2.y1 && line1.y2<=line2.y1) {
							continue;
						}
						lines.remove(line1);
						i--;
						lines.remove(line2);
						j--;
						continue out;
					}
					if(line1.y1==line2.y1 && line1.y1==line1.y2 && line2.y1==line2.y2) {
						if(line1.x1>=line2.x2 && line1.x2>=line2.x2) {
							continue;
						}
						if(line1.x1<=line2.x1 && line1.x2<=line2.x1) {
							continue;
						}
						lines.remove(line1);
						i--;
						lines.remove(line2);
						j--;
						continue out;
					}
					if(line1.isVertical() && !line2.isVertical()) {
						if(line1.y1<line2.y1 && line1.y2>line2.y1 && line2.x1<line1.x1 && line2.x2>line1.x1) {
							lines.remove(line1);
							i--;
							lines.remove(line2);
							j--;
							continue out;
						}
					}
					if(!line1.isVertical() && line2.isVertical()) {
						if(line2.y1<line1.y1 && line2.y2>line1.y1 && line1.x1<line2.x1 && line1.x2>line2.x1) {
							lines.remove(line1);
							i--;
							lines.remove(line2);
							j--;
							continue out;
						}
					}
				}
			}
		}
		*/		
		sortLines(this.lines);
	}
	public static void sortLines(List<Line> lines) {
		boolean ok=false;
		while(!ok) {
			ok=true;
			for(int i=0;i<lines.size();i++) {
				for(int j=0;j<lines.size();j++) {
					Line one=lines.get(i);
					Line two=lines.get(j);
					if((i<j && one.compareTo(two)>0) || (i>j && one.compareTo(two)<0)) {
						ok=false;
						lines.set(i,two);
						lines.set(j,one);
					}
				}
			}
		}
	}
	public void writeObject(ObjectOutputStream out) throws IOException {		
		this.validLines();
		out.writeInt(blockrows);
		out.writeInt(blockcolumns);
		out.writeInt(manx);
		out.writeInt(many);
		out.writeInt(doorx);
		out.writeInt(doory);
		out.writeInt(mummies.size());
		for(Mummy mummy:mummies) {
			mummy.writeObject(out);
		}
		out.writeInt(lines.size());
		for(Line line:lines) {
			line.writeObject(out);
		}
		out.writeInt(scorpions.size()); 
		for(Scorpion scorpion:scorpions) {
			scorpion.writeObject(out);
		}
		out.writeInt(traps.size());
		for(Trap trap:traps) {
			trap.writeObject(out);
		}
		out.writeInt(toggles.size());
		for(ToggleLine toggle:toggles) {
			toggle.writeObject(out);
		}
	}
	public static Maze readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {
		Mummy mummyTemp;
		Line lineTemp;
		Scorpion scorpionTemp;
		Trap trapTemp;
		ToggleLine toggleTemp;
		Utilities.getInstance().log("Maze-readObject- BEGIN");
		Maze maze=new Maze();
		maze.blockrows=in.readInt();
		Utilities.getInstance().log("blockrows="+String.valueOf(maze.blockrows));
		maze.blockcolumns=in.readInt();
		Utilities.getInstance().log("blockcolumns="+String.valueOf(maze.blockcolumns));
		maze.manx=in.readInt();
		maze.many=in.readInt();
		Utilities.getInstance().log("explorer(x,y)="+String.valueOf(maze.manx)+","+String.valueOf(maze.many));
		maze.explorer=new Explorer(maze.manx, maze.many);
		maze.doorx=in.readInt();
		maze.doory=in.readInt();
		Utilities.getInstance().log("door(x,y)="+String.valueOf(maze.doorx)+","+String.valueOf(maze.doory));
		
		int n=in.readInt();
		Utilities.getInstance().log("No of Mummy="+String.valueOf(n));

		for(int i=0;i<n;i++) {
			mummyTemp=Mummy.readObject(in);
			maze.mummies.add(mummyTemp);
			maze.mummiesHash.put(i,mummyTemp);
			Utilities.getInstance().log(String.valueOf(i)+": "+ String.valueOf(mummyTemp.x)+","+String.valueOf(mummyTemp.y));
		}
		n=in.readInt();

		Utilities.getInstance().log("No of Line="+String.valueOf(n));
		for(int i=0;i<n;i++) {
			maze.lines.add(Line.readObject(in));
		}
		n=in.readInt();
		Utilities.getInstance().log("No of Scorpion="+String.valueOf(n));

		for(int i=0;i<n;i++) {
			scorpionTemp=Scorpion.readObject(in);
			maze.scorpions.add(scorpionTemp);
			maze.scorpionsHash.put(i, scorpionTemp);
			Utilities.getInstance().log(String.valueOf(i)+": "+ String.valueOf(scorpionTemp.x)+","+String.valueOf(scorpionTemp.y));
		}
		n=in.readInt();

		Utilities.getInstance().log("No of Trap="+String.valueOf(n));
		for(int i=0;i<n;i++) {
			trapTemp=Trap.readObject(in);
			maze.traps.add(trapTemp);
			Utilities.getInstance().log(String.valueOf(i)+": "+ String.valueOf(trapTemp.startx)+","+String.valueOf(trapTemp.starty));
		}
		n=in.readInt();

		for(int i=0;i<n;i++) {
			toggleTemp=ToggleLine.readObject(in);
			maze.toggles.add(toggleTemp);
			maze.togglesHash.put(i, toggleTemp);
		}
		Utilities.getInstance().log("Maze- readObject - END");
		return maze;
	}
	public boolean canMove(Character character, Direction direction)
	{
		//Utilities.getInstance().log(character.getClass().getName());
		Point pos=character.getPosition();
		
		if (direction==Direction.UP)
		{
			if (pos.y<=0 || isBlocked(new Line(pos.x,pos.y,pos.x+1,pos.y)))
				return false;
		}
		else if (direction==Direction.DOWN)
		{
			if (pos.y>=blockrows-1 || isBlocked(new Line(pos.x,pos.y+1,pos.x+1,pos.y+1)))
				return false;
			
		}
		else if (direction==Direction.LEFT)
		{
			if (pos.x<=0|| isBlocked(new Line(pos.x,pos.y,pos.x,pos.y+1)))
				return false;
			
		}
		else if (direction==Direction.RIGHT)
		{
			if (pos.x>=blockcolumns-1 || isBlocked(new Line(pos.x+1,pos.y,pos.x+1,pos.y+1)))
				return false;
		}
		else if (direction==Direction.WAIT)
		{
			return canWait(explorer);
			
		}
		return true;
	}
	//Check if the input line is belong to lines
	private boolean isBlocked(Line line)
	{
		for(Line item:lines)
		{
			if (item.equals(line))
				return true;
		}
		for(ToggleLine item:toggles)
		{
			if (item.equals(line)==true && item.state==MummyConst.LOCK)
				return true;
		}
		return false;
	}
	
	public boolean isLose()
	{
		for(Mummy mummy:mummies)
		{
			if(mummy.getPosition().equals(explorer.getPosition()))
			{
				return true;
			}
		}
		for(Scorpion scorpion:scorpions)
		{
			if(scorpion.getPosition().equals(explorer.getPosition()))
			{
				return true;
			}
		}
		for(Trap trap:traps)
		{
			if(trap.getPosition().equals(explorer.getPosition()))
			{
				return true;
			}
		}
		return false;
	}
	
	public boolean isWin()
	{
		if (isLose()==false)
		{
			if (doorx==explorer.x)
			{
				if (Math.abs(doory-explorer.y)==1)
				return true;
			}
			else if (doory==explorer.y)
			{
				if (Math.abs(doorx-explorer.x)==1)
				return true;
			}
			return false;
		}
		else return false;
	}
	
	public String createSignature()
	{
		String mP=String.valueOf(explorer.x)+String.valueOf(explorer.y);
		for(int i=0;i<mummies.size();i++)
		{
			mP+=String.valueOf(mummiesHash.get(i).x)+String.valueOf(mummiesHash.get(i).y);
		}
		mP+="/";
		for(int i=0;i<scorpionsHash.size();i++)
		{
			mP+=String.valueOf(scorpionsHash.get(i).x)+String.valueOf(scorpionsHash.get(i).y);
		}
		mP+="/";
		for(int i=0;i<togglesHash.size();i++)
		{
			mP+=String.valueOf(togglesHash.get(i).togglex)+String.valueOf(togglesHash.get(i).toggley+String.valueOf(togglesHash.get(i).state));
		}
		mP+="/";
		for(int i=0;i<lines.size();i++)
		{
			mP+=lines.get(i).toString();
		}
		return mP;
	}
	 
	private boolean canWait(Character explorer)
	{
		Character character=null;
		boolean ret=false;
		
		for(int i=0;i<mummiesHash.size();i++)
		{
			character=mummiesHash.get(i);
			ret=checkOpponentCanMoveIfExplorerWait(character, explorer);
			if (ret==true)
				return true;
		}
		for(int i=0;i<scorpionsHash.size();i++)
		{
			character=scorpionsHash.get(i);
			ret=checkOpponentCanMoveIfExplorerWait(character, explorer);
			if (ret==true)
				return true;
		}
		return ret;
				
	}
	
	public boolean checkOpponentCanMoveIfExplorerWait(Character opponent,Character explorer)
	{
		int vision=PositionManager.getVision(opponent, explorer);
		switch (vision) {
		case 1:
			return canMove(opponent, Direction.RIGHT)||canMove(opponent, Direction.DOWN);
		case 2:
			return canMove(opponent, Direction.DOWN);
		case 3:
			return canMove(opponent, Direction.LEFT)||canMove(opponent, Direction.DOWN);
		case 4:
			return canMove(opponent, Direction.LEFT);
		case 5:
			return canMove(opponent, Direction.LEFT)||canMove(opponent, Direction.UP);
		case 6:
			return canMove(opponent, Direction.UP);
		case 7:
			return canMove(opponent, Direction.RIGHT)||canMove(opponent, Direction.UP);
		case 8:
			return canMove(opponent, Direction.RIGHT);
		default:
			return false;
		}
	}
	
	public static void main(String[] args) throws Exception {
		try {
			File parent=new File("C:\\Users\\Phan Quang Trung\\workspace\\MummyMaze\\bin\\data");
			File[] files=parent.listFiles();
			int n=0;
			for(File file:files) {
				System.out.println(++n);
				System.out.println(file);
				ObjectInputStream input=new ObjectInputStream(new FileInputStream(file));
				Maze maze=Maze.readObject(input);
				input.close();				
				ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream(file));
				maze.writeObject(output);
				output.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}